// Copyright (c) 2008 Matthew Johnson
// Licensed under the MIT License (see license.txt)
package actionml.interaction {
	import actionml.Global;

	import flash.display.DisplayObject;
	import flash.events.FocusEvent;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.ui.Keyboard;

	public class DragAndDropInteractions {
		public static function initialize() {
			Global.initializeExtension(DragAndDropInteractions, DisplayObject);
		}
		public static var instanceMethods:Object = {
			makeDropTarget: function(options:Object = null) {
				if (this.isAlready("DropTarget")) return;
				options ||= {};
				this.root["dropTargets"] ||= [];
				this.root["dropTargets"].push(this);
				if (this.overIndicator) {
					this.overIndicator.visible = false;
				}
				if (this.dropIndicator) {
					this.dropIndicator.visible = false;
				}
				if (this.invalidDropIndicator) {
					this.invalidDropIndicator.visible = false;
				}
				if (this.dropMarker) {
					this.dropMarker.visible = false;
				}
				for (var i:int = 0; i < (this.numChildren - 1); i++) {
					var child:DisplayObject = this.getChildAt(i);
					if ("name" in child && child.name && child.name.endsWith("DropMarker")) {
						child.visible = false;
					}
				}
			},
			beginDrag: function() {
				if (!this.dragging) {
					this.moveToFront();
					this.originalX = this.x;
					this.originalY = this.y;
					this.dragging = true;
					this.movedByKeyboard = false;
				}
			},
			updateDrag: function() {
				var dropTarget:DisplayObject;
				var validDropTarget:Boolean = false;
				for each (var target:DisplayObject in this.root["dropTargets"]) {
					if (this.hitTestObject(target)) {
						dropTarget = target;
					}
				}
				for each (var validTarget:DisplayObject in this.dropTargets) {
					if (this.hitTestObject(validTarget)) {
						dropTarget = validTarget;
						validDropTarget = true;
					}
				}
				if (dropTarget && dropTarget != this.currentDropTarget) {
					if (this.currentDropTarget) {
						if (this.currentDropTarget.overIndicator) {
							this.currentDropTarget.overIndicator.visible = false;
						}
						this.currentDropTarget.doEvent("dragOut");
						this.doEvent("dragOut");
					}
					if (dropTarget.overIndicator) {
						dropTarget.overIndicator.visible = true;
					}
					this.currentDropTarget = dropTarget;
					this.currentDropTargetIsValid = validDropTarget;
					this.currentDropTarget.doEvent("dragOver");
					this.doEvent("dragOver");
				} else if (!dropTarget && this.currentDropTarget) {
					if (this.currentDropTarget.overIndicator) {
						this.currentDropTarget.overIndicator.visible = false;
					}
					this.currentDropTarget = null;
				}
			},
			endDrag: function() {
				this.updateDrag();
				this.movedByKeyboard = false;
				if (this.currentDropTarget && this.currentDropTarget.overIndicator) {
					this.currentDropTarget.overIndicator.visible = false;
				}
				if (this.currentDropTarget && this.currentDropTargetIsValid) {
					this.dropHit();
				} else {
					this.dropMiss();						
				}	
				this.dragging = false;
			},
			abortDrag: function() {
				this.doEvent("abortDrag");
				var o:DisplayObject = this;
				this.playTween({x: this.originalX, y: this.originalY, duration: 0.5, callWhenFinished: function() {o.updateFocusIndicator();}});
			},
			dropMiss: function() {
				this.doEvent("dropMiss");
				var o:DisplayObject = this;
				this.playTween({x: this.originalX, y: this.originalY, duration: 0.5, callWhenFinished: function() {o.updateFocusIndicator();}});
				if (this.currentDropTarget && this.currentDropTarget.invalidDropIndicator) {
					this.currentDropTarget.invalidDropIndicator.visible = true;
					this.currentDropTarget.invalidDropIndicator.alpha = 0;
					this.currentDropTarget.invalidDropIndicator.playTween({startAlpha:0, alpha:1})
						.playWhenFinished({alpha:0});
				}
				if (this.invalidDropIndicator) {
					this.invalidDropIndicator.visible = true;
					this.invalidDropIndicator.alpha = 0;
					this.invalidDropIndicator.playTween({startAlpha:0, alpha:1})
						.playWhenFinished({alpha:0});
				}
			},
			dropHit: function() {
				this.currentDropTarget.doEvent("dropHit");
				var o:DisplayObject = this;
				if (this.currentDropTarget && this.currentDropTarget.dropIndicator) {
					this.currentDropTarget.dropIndicator.visible = true;
					this.currentDropTarget.dropIndicator.alpha = 0;
					this.currentDropTarget.dropIndicator.playTween({startAlpha:0, alpha:1})
						.playWhenFinished({alpha:0});
				}
				if (this.dropIndicator) {
					this.dropIndicator.visible = true;
					this.dropIndicator.alpha = 0;
					this.dropIndicator.playTween({startAlpha:0, alpha:1})
						.playWhenFinished({alpha:0});
				}
				var x:Number = this.currentDropTarget[this.name + "X"];
				if (!x && this.currentDropTarget[this.name + "DropMarker"]) {
					x = this.currentDropTarget.getPositionInCoordinateSpaceOfObject(this.parent).x +
						this.currentDropTarget[this.name + "DropMarker"].x;
				}
				if (!x) {
					x = this.currentDropTarget.dropX;
				}
				if (!x && this.currentDropTarget.dropMarker) {
					x = this.currentDropTarget.getPositionInCoordinateSpaceOfObject(this.parent).x +
						this.currentDropTarget.dropMarker.x;
				}
				if (!x) {
					x = this.currentDropTarget.x + (this.currentDropTarget.width - this.width) / 2;
				}
				var y:Number = this.currentDropTarget[this.name + "Y"];
				if (!y && this.currentDropTarget[this.name + "DropMarker"]) {
					y = this.currentDropTarget.getPositionInCoordinateSpaceOfObject(this.parent).y +
						this.currentDropTarget[this.name + "DropMarker"].y;
				}
				if (!y) {
					y = this.currentDropTarget.dropY;
				}
				if (!y && this.currentDropTarget.dropMarker) {
					x = this.currentDropTarget.getPositionInCoordinateSpaceOfObject(this.parent).y +
						this.currentDropTarget.dropMarker.x;
				}
				if (!y) {
					y = this.currentDropTarget.y + (this.currentDropTarget.height - this.height) / 2;
				}
				this.playTween({
					x: x, 
					y: y,
					duration: 0.5, 
					callWhenFinished: function() {
						o.doEvent("dropHit");
					}
				});	
				this.hit.removeEventListener(MouseEvent.MOUSE_DOWN, this.mouseDownDragHandler);
				this.hit.buttonMode = false;
				this.hit.useHandCursor = false;
				this.tabEnabled = false;
				this.forceMouseOut();
				this.blockMouseFocus();
			},
			makeDragItem: function(options:Object = null) {
				if (this.isAlready("DragItem")) return;
				options ||= {};
				options["dropTargets"] ||= "";
				this.labelText = this.findChild("labelText", options);
				if (this.labelText) {
					this.getLabel = function() {
						return this.labelText.text;
					};
					this.setLabel = function(label:String) {
						this.labelText.text = label;
					};
				}
				if ("label" in options) {	
					if (this.labelText) {
						this.setLabel(options["label"]);
					} else {
						// TODO: trace no label text error
					}
				}
				this.hit = this.findChild("hit", options) || this;
				if (this.dropIndicator) {
					this.dropIndicator.visible = false;
				}
				if (this.invalidDropIndicator) {
					this.invalidDropIndicator.visible = false;
				}
				this.dropTargets = [];
				for each (var dropTarget in options["dropTargets"].split(/\s*,\s*/)) {
					this.dropTargets.push(this.getValueForKeypath(dropTarget));
				}
				this.hit.buttonMode = true;
				this.hit.useHandCursor = true;
				this.tabEnabled = true;
				if (options && options.focusIndicatorName is String) {
					this.displayIndicatorWhenFocused(options.focusIndicatorName);
				} else {
					this.displayIndicatorWhenFocused();
				}
				var o:DisplayObject = this;
				this.mouseDownDragHandler = function(e:MouseEvent) {
					o.beginDrag();
					o.startDrag();
					var mouseDownHandler:Function = arguments.callee;
					Global.stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
					function mouseMoveHandler(e:MouseEvent) {
						o.updateDrag();
					}
					Global.stage.addEventListener(MouseEvent.MOUSE_UP, function(e:MouseEvent) {
						o.stage.focus = null;
						o.endDrag();
						o.stopDrag();
						Global.stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
						Global.stage.removeEventListener(MouseEvent.MOUSE_UP, arguments.callee);
					});
				}
				this.hit.addEventListener(MouseEvent.MOUSE_DOWN, this.mouseDownDragHandler);
				this.addEventListener(FocusEvent.FOCUS_OUT, function(e:FocusEvent) {
					if (o.movedByKeyboard) {
						o.abortDrag();
					}
				});
				this.addEventListener(KeyboardEvent.KEY_DOWN, function(e:KeyboardEvent) {
					if (e.keyCode == Keyboard.UP) {
						o.beginDrag();
						o.movedByKeyboard = true;
						o.y--;
						if (e.ctrlKey) {
							o.y -= 9;
						}
						o.updateDrag();
					}
					if (e.keyCode == Keyboard.DOWN) {
						o.beginDrag();
						o.movedByKeyboard = true;
						o.y++;
						if (e.ctrlKey) {
							o.y += 9;
						}
						o.updateDrag();
					}
					if (e.keyCode == Keyboard.RIGHT) {
						o.beginDrag();
						o.movedByKeyboard = true;
						o.x++;
						if (e.ctrlKey) {
							o.x += 9;
						}
						o.updateDrag();
					}
					if (e.keyCode == Keyboard.LEFT) {
						o.beginDrag();
						o.movedByKeyboard = true;
						o.x--;
						if (e.ctrlKey) {
							o.x -= 9;
						}
						o.updateDrag();
					}
					if (e.keyCode == Keyboard.ENTER) {
						o.endDrag();
					}
					if (e.keyCode == Keyboard.ESCAPE) {
						o.abortDrag();
					}
				});
			}
		}
	}
}